package com.yeb.server.config.security;


import com.yeb.server.pojo.Admin;
import com.yeb.server.service.IAdminService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;


/**
 * spring security 环境配置
 */
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private IAdminService adminService;
    @Autowired
    private RestAuthorizationEntryPoint restAuthorizationEntryPoint;
    @Autowired
    private RestfulAccessDeniedHandler restfulAccessDeniedHandler;
    @Autowired
    private CustomFilter customFilter;
    @Autowired
    private CustomUrlDecisionManager customUrlDecisionManager;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        // 定义使用自身认证组件，登录逻辑验证用自身定义的
        // 添加 userDetailsService 登入逻辑，并设置密码匹配机制
        auth.userDetailsService(userDetailsService()).passwordEncoder(passwordEncoder());
    }


    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //cors解决跨域问题
        http.cors()
                .and()
                // 这里使用 jwt 因此禁用 scrf，csrf 会对 PATCH，POST，PUT和DELETE方法进行防护，即使已经登录，也会防护
                .csrf().disable()
                // 基于 token 不需要 session, STATELESS: 永远不会创建 HttpSession
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                // 请求全部认证(登录)
                .authorizeRequests().anyRequest().authenticated()
                /**
                 * 所有的请求都经过 FilterInvocationSecurityMetadataSource 与 AccessDecisionManager
                 * 1.如果没有登录访问
                 *  - 若访问的地址不存在 返回 ROLE_LOGIN ,身份未认证，AccessDecisionManager就会抛出一个异常 throw new AccessDeniedException("尚未登录，请登录") ,然后进入loginPage的url
                 *  - 若访问的地址存在，返回一个 Collection<ConfigAttribute>对象，遍历用户时，无此角色，抛出异常，然后进入loginPage的url
                 * 2.如果登录访问时
                 * - 若访问的地址不存在 ，返回 ROLE_LOGIN，身份已认证，直接return，404 不做任何处理
                 * - 若访问的地址存在，返回一个 Collection<ConfigAttribute>对象，遍历对象时，有所需的角色直接return，不做任何处理。若没有，抛出异常 403 throw new AccessDeniedException("权限不足，请联系管理员");
                 *
                 **/
                // 动态权限配置
                .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
                    @Override
                    public <O extends FilterSecurityInterceptor> O postProcess(O o) {
                        o.setAccessDecisionManager(customUrlDecisionManager);
                        o.setSecurityMetadataSource(customFilter);
                        return o;
                    }
                })
                .and()
                // 禁用缓存，防止将 Cache 中保存的数据直接返回给用户
                .headers().cacheControl();

        // jwt 登录授权过滤器
        http.addFilterBefore(jwtAuthenticationFilter(),UsernamePasswordAuthenticationFilter.class);
        // 未授权与未登录结果返回
        http.exceptionHandling()
                // 权限不足处理
                .accessDeniedHandler(restfulAccessDeniedHandler)
                // 用户未登入处理
                .authenticationEntryPoint(restAuthorizationEntryPoint);
    }

    /**
     * 自定义的登录逻辑，返回 UserDetails 的实现类 Admin
     * @return
     */
    @Bean  // 这里注入 UserDetailsService 对象
    @Override
    public UserDetailsService userDetailsService(){ // 重写 loadUserByUsername 参数为用户名
        return username -> {
            Admin admin = adminService.getAdminByUsername(username);
            if(null!=admin){
                // 设置用户角色
                admin.setRoles(adminService.getRoles(admin.getId()));
                return admin; // 这里的 admin 实现了 UserDetails 接口
            }
            throw new UsernameNotFoundException("用户名或密码不正确");
        };
    }

    /**
     * 放行某些资源，不经过拦截器
     * @param web
     * @throws Exception
     */
    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring()
                .antMatchers(
                        "/css/**",
                        "/js/**",
                        "/index.html",
                        "favicon.ico",
                        "/doc.html",
                        "/webjars/**",
                        "/swagger-resources/**",
                        "/v2/api-docs/**",
                        "/login",
                        "/logout",
                        "/captcha",
                        "/ws/**"
                );
    }


    @Bean  // 注入密码匹配
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder(); // PasswordEncoder 接口的实现类 BCryptPasswordEncoder
    }

    @Bean // 注入登录认证过滤器
    public JwtAuthenticationFilter jwtAuthenticationFilter(){
        return new JwtAuthenticationFilter();
    }
}
